package org.gikolet.ch2.android.widget;

import org.gikolet.ch2.android.viewer.ViewChangeListener;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.OvershootInterpolator;
import android.widget.Scroller;

public class HorizontalScroller extends ViewGroup {
	public static final int INVALID_VIEW_INDEX = -1;

	private static final int SNAP_VELOCITY = 1000;

	private int defaultViewIndex;
	private boolean firstLayout = true;

	private int currentViewIndex;
	private int nextViewIndex = INVALID_VIEW_INDEX;
	private Scroller scroller;
	private VelocityTracker mVelocityTracker;

	private float lastMotionX;
	private float lastMotionY;

	private final static int TOUCH_STATE_NONE = 0;
	private final static int TOUCH_STATE_SCROLLING = 1;

	private int touchState = TOUCH_STATE_NONE;

	private boolean allowLongPress;
	private boolean locked;

	private int touchSlop;

	private ViewChangeListener viewChangeListener;

	public HorizontalScroller(Context context, AttributeSet attrs) {
		this(context, attrs, 0, 0);

	}

	public HorizontalScroller(Context context, AttributeSet attrs,
			int defStyle, int defaultView) {
		super(context, attrs, defStyle);

		this.defaultViewIndex = defaultView;

		this.scroller = new Scroller(getContext(),
				new OvershootInterpolator(3f));
		this.nextViewIndex = defaultViewIndex;
		computeScroll();

		// this.touchSlop = ViewConfiguration.get(getContext())
		// .getScaledTouchSlop();
		this.touchSlop = 50;
	}

	public void setViewChangeListener(ViewChangeListener l) {
		this.viewChangeListener = l;
	}

	public ViewChangeListener getViewChangeListener() {
		return this.viewChangeListener;
	}

	protected void fireViewChanged(int oldViewIndex, int newOldViewIndex) {
		View oldView = null;
		if (oldViewIndex != INVALID_VIEW_INDEX) {
			oldView = getChildAt(oldViewIndex);
		}

		View newView = null;
		if (newOldViewIndex != INVALID_VIEW_INDEX) {
			newView = getChildAt(newOldViewIndex);
		}

		ViewChangeListener l = this.viewChangeListener;
		if (l != null) {
			l.viewChanged(this, oldView, newView);
		}
	}

	public boolean isDefaultViewShowing() {
		return this.currentViewIndex == this.defaultViewIndex;
	}

	public View getCurrentView() {
		return getChildAt(this.currentViewIndex);
	}

	public int getCurrentViewIndex() {
		return this.currentViewIndex;
	}

	public void setCurrentView(int index) {
		this.currentViewIndex = Math.max(0, Math
				.min(index, getChildCount() - 1));
		scrollTo(this.currentViewIndex * getWidth(), 0);
		invalidate();
	}

	public void showDefaultScreen() {
		setCurrentView(this.defaultViewIndex);
	}

	@Override
	public void computeScroll() {
		if (this.scroller.computeScrollOffset()) {
			scrollTo(this.scroller.getCurrX(), this.scroller.getCurrY());
			postInvalidate();

		} else if (this.nextViewIndex != INVALID_VIEW_INDEX) {
			int oldViewIndex = this.currentViewIndex;

			this.currentViewIndex = Math.max(0, Math.min(this.nextViewIndex,
					getChildCount() - 1));
			this.nextViewIndex = INVALID_VIEW_INDEX;

			View oldView = getChildAt(oldViewIndex);
			if (oldView != null) {
				oldView.clearFocus();
			}

			View newView = getChildAt(this.currentViewIndex);
			if (newView != null) {
				// newView.setFocusable(true);
				// newView.requestFocus();
			}

			fireViewChanged(oldViewIndex, this.currentViewIndex);
		}
	}

	// @Override
	// protected void dispatchDraw(Canvas canvas) {
	// boolean restore = false;
	//
	// boolean fastDraw = this.touchState != TOUCH_STATE_SCROLLING
	// && this.nextViewIndex == INVALID_VIEW_INDEX;
	//
	// if (fastDraw) {
	// drawChild(canvas, getChildAt(this.currentViewIndex),
	// getDrawingTime());
	//
	// } else {
	// final long drawingTime = getDrawingTime();
	//
	// if (this.nextViewIndex >= 0
	// && this.nextViewIndex < getChildCount()
	// && Math.abs(this.currentViewIndex - this.nextViewIndex) == 1) {
	// drawChild(canvas, getChildAt(this.currentViewIndex),
	// drawingTime);
	// drawChild(canvas, getChildAt(this.nextViewIndex), drawingTime);
	//
	// } else {
	// final int count = getChildCount();
	// for (int i = 0; i < count; i++) {
	// drawChild(canvas, getChildAt(i), drawingTime);
	// }
	// }
	// }
	//
	// if (restore) {
	// canvas.restore();
	// }
	// }

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		final int width = MeasureSpec.getSize(widthMeasureSpec);
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

		if (widthMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(getClass().getName()
					+ " can only be used in EXACTLY mode.");
		}

		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		if (heightMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(getClass().getName()
					+ " can only be used in EXACTLY mode.");
		}

		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}

		if (this.firstLayout) {
			scrollTo(this.currentViewIndex * width, 0);
			this.firstLayout = false;
		}
	}

	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
		int count = getChildCount();

		int r = l + getRight();

		for (int i = 0; i < count; i++) {
			View v = getChildAt(i);

			if ((v.getLeft() <= l && l < v.getRight())
					|| (v.getLeft() < r && r <= v.getRight())) {
				if (v.getVisibility() != View.VISIBLE) {
					v.setVisibility(View.VISIBLE);
				}
				if (!v.isEnabled()) {
					v.setEnabled(true);
				}

			} else {
				if (v.getVisibility() != View.INVISIBLE) {
					v.setVisibility(View.INVISIBLE);
				}
				if (v.isEnabled()) {
					v.setEnabled(false);
				}
			}
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		int childLeft = 0;

		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() != View.GONE) {
				child.layout(childLeft, 0, childLeft + getWidth(), getHeight());
				childLeft += getWidth();
			}
		}
	}

	@Override
	public boolean dispatchUnhandledMove(View focused, int direction) {
		System.out.println(focused);
		if (direction == View.FOCUS_LEFT) {
			if (getCurrentViewIndex() > 0) {
				snapToScreen(getCurrentViewIndex() - 1);
				return true;
			}

		} else if (direction == View.FOCUS_RIGHT) {
			if (getCurrentViewIndex() < getChildCount() - 1) {
				snapToScreen(getCurrentViewIndex() + 1);
				return true;
			}
		}
		return false;
		// return super.dispatchUnhandledMove(focused, direction);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (locked) {
			return true;
		}

		/*
		 * This method JUST determines whether we want to intercept the motion.
		 * If we return true, onTouchEvent will be called and we do the actual
		 * scrolling there.
		 */

		/*
		 * Shortcut the most recurring case: the user is in the dragging state
		 * and he is moving his finger. We want to intercept this motion.
		 */
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (touchState != TOUCH_STATE_NONE)) {
			return true;
		}

		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_MOVE:
			/*
			 * mIsBeingDragged == false, otherwise the shortcut would have
			 * caught it. Check whether the user has moved far enough from his
			 * original down touch.
			 */

			/*
			 * Locally do absolute value. mLastMotionX is set to the y value of
			 * the down event.
			 */
			final int xDiff = (int) Math.abs(x - lastMotionX);
			final int yDiff = (int) Math.abs(y - lastMotionY);

			final int touchSlop = this.touchSlop;
			boolean xMoved = xDiff > touchSlop;
			boolean yMoved = yDiff > touchSlop;

			if (xMoved || yMoved) {

				if (xMoved) {
					// Scroll if the user moved far enough along the X axis
					touchState = TOUCH_STATE_SCROLLING;
				}
				// Either way, cancel any pending longpress
				if (allowLongPress) {
					allowLongPress = false;
					// Try canceling the long press. It could also have been
					// scheduled
					// by a distant descendant, so use the mAllowLongPress flag
					// to block
					// everything
					final View currentScreen = getChildAt(currentViewIndex);
					currentScreen.cancelLongPress();
				}
			}
			break;

		case MotionEvent.ACTION_DOWN:
			// Remember location of down touch
			lastMotionX = x;
			lastMotionY = y;
			firstX = x;
			allowLongPress = true;

			/*
			 * If being flinged and user touches the screen, initiate drag;
			 * otherwise don't. mScroller.isFinished should be false when being
			 * flinged.
			 */
			touchState = scroller.isFinished() ? TOUCH_STATE_NONE
					: TOUCH_STATE_SCROLLING;
			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			// Release the drag
			touchState = TOUCH_STATE_NONE;
			allowLongPress = false;
			break;
		}

		/*
		 * The only time we want to intercept motion events is if we are in the
		 * drag mode.
		 */
		return touchState != TOUCH_STATE_NONE;
	}

	float firstX;

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (locked) {
			return true;
		}

		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();
		final float x = ev.getX();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			/*
			 * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
			if (!scroller.isFinished()) {
				scroller.abortAnimation();
			}

			// Remember where the motion event started
			lastMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			if (touchState == TOUCH_STATE_SCROLLING) {
				// Scroll to follow the motion event
				final int deltaX = (int) (lastMotionX - x);
				lastMotionX = x;

				if (deltaX < 0) {
					if (getScrollX() > 0) {
						scrollBy(Math.max(-getScrollX(), deltaX), 0);
					}
				} else if (deltaX > 0) {
					final int availableToScroll = getChildAt(
							getChildCount() - 1).getRight()
							- getScrollX() - getWidth();
					if (availableToScroll > 0) {
						scrollBy(Math.min(availableToScroll, deltaX), 0);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (touchState == TOUCH_STATE_SCROLLING) {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000);
				int velocityX = (int) velocityTracker.getXVelocity();

				if (velocityX > SNAP_VELOCITY && currentViewIndex > 0) {
					// Fling hard enough to move left
					snapToScreen(currentViewIndex - 1);
				} else if (velocityX < -SNAP_VELOCITY
						&& currentViewIndex < getChildCount() - 1) {
					// Fling hard enough to move right
					snapToScreen(currentViewIndex + 1);
				} else {
					snapToDestination();
				}

				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
			}
			touchState = TOUCH_STATE_NONE;
			break;
		case MotionEvent.ACTION_CANCEL:
			touchState = TOUCH_STATE_NONE;
		}

		return true;
	}

	private void snapToDestination() {
		final int screenWidth = getWidth();
		final int whichScreen = (getScrollX() + (screenWidth / 2))
				/ screenWidth;

		snapToScreen(whichScreen);
	}

	void snapToScreen(int whichScreen) {

		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		boolean changingScreens = whichScreen != currentViewIndex;

		nextViewIndex = whichScreen;

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingScreens
				&& focusedChild == getChildAt(currentViewIndex)) {
			focusedChild.clearFocus();
		}

		final int newX = whichScreen * getWidth();
		final int delta = newX - getScrollX();
		scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	public void scrollLeft() {
		if (nextViewIndex == INVALID_VIEW_INDEX && currentViewIndex > 0
				&& scroller.isFinished()) {
			snapToScreen(currentViewIndex - 1);
		}
	}

	public void scrollRight() {
		if (nextViewIndex == INVALID_VIEW_INDEX
				&& currentViewIndex < getChildCount() - 1
				&& scroller.isFinished()) {
			snapToScreen(currentViewIndex + 1);
		}
	}

	public int getScreenForView(View v) {
		int result = -1;
		if (v != null) {
			ViewParent vp = v.getParent();
			int count = getChildCount();
			for (int i = 0; i < count; i++) {
				if (vp == getChildAt(i)) {
					return i;
				}
			}
		}
		return result;
	}

	/**
	 * Unlocks the SlidingDrawer so that touch events are processed.
	 *
	 * @see #lock()
	 */
	public void unlock() {
		locked = false;
	}

	/**
	 * Locks the SlidingDrawer so that touch events are ignores.
	 *
	 * @see #unlock()
	 */
	public void lock() {
		locked = true;
	}

	/**
	 * @return True is long presses are still allowed for the current touch
	 */
	public boolean allowLongPress() {
		return allowLongPress;
	}

	/**
	 * Set true to allow long-press events to be triggered, usually checked by
	 * {@link Launcher} to accept or block dpad-initiated long-presses.
	 */
	public void setAllowLongPress(boolean allowLongPress) {
		this.allowLongPress = allowLongPress;
	}

	void moveToDefaultScreen() {
		snapToScreen(defaultViewIndex);
		getChildAt(defaultViewIndex).requestFocus();
	}
}